﻿using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    public class ContinuousGroups
    {
        public List<List<int>> GenerateCombinations(List<Group> groups)
        {
            List<List<int>> results = new List<List<int>>();
            int bestScore = -1;
            int maxScore = groups.Count - 1;

            List<IEnumerable<List<int>>> generators = new List<IEnumerable<List<int>>>();
            int groupsCount = groups.Count;

            List<GroupSolution> stack = new List<GroupSolution>();
            List<GroupSolution> partialSolutions = new List<GroupSolution>();

            List<int> previous = null;
            for (int i = 0; i < groupsCount; i++)
            {

                var next = i == groupsCount - 1 ? null : groups[i + 1].Items.Intersect(groups[i].Items).ToList();
                partialSolutions.Add(new GroupSolution(groups[i], previous, next));
                previous = next;
            }

            stack.Push(partialSolutions[0]);
            while (true)
            {
                while (stack.Count > 0)
                {
                    var top = stack.Peek();
                    if (top.MoveNext())
                        break;

                    top.Reset();
                    stack.Pop();
                }

                var stackCount = stack.Count;
                if (stack.Count == 0)
                {
                    break;
                }

                if (stack.Count == groupsCount)
                {
                    var result = stack.SelectMany(a => a.Current());
                    var score = GetScore(stack);
                    if (score > bestScore)
                    {
                        bestScore = score;
                        results = stack.Select(a => a.Current()).ToList();
                    }
                    if (score == maxScore)
                    {
                        break;
                    }
                    continue;
                }

                var partialSolution = partialSolutions[stackCount];
                stack.Push(partialSolution);
            }
            return results;
        }

        private int GetScore(List<GroupSolution> stack)
        {
            int score = 0;
            for (int i = 0; i < stack.Count - 1; i++)
            {
                var end = stack[i];
                var start = stack[i + 1];

                if (end.Current()[end.Current().Count - 1] == start.Current()[0])
                    ++score;
            }

            return score;
        }

        private class GroupSolution
        {
            IEnumerator<List<int>> _currentIterator;
            private readonly IEnumerable<List<int>> _solutions;

            public GroupSolution(Group grp, List<int> before, List<int> after)
            {
                _solutions = Enumerate(grp, before ?? new List<int>(0), after ?? new List<int>(0));
                _currentIterator = _solutions.GetEnumerator();
            }

            public void Reset()
            {
                _currentIterator = _solutions.GetEnumerator();
            }

            public bool MoveNext()
            {
                return _currentIterator.MoveNext();
            }

            public List<int> Current()
            {
                return _currentIterator.Current;
            }

            private IEnumerable<List<int>> Enumerate(Group g1, List<int> startList, List<int> endList)
            {
                int endIndex = g1.Items.Count - 1;

                if (endIndex <= 0)
                {

                    yield return g1.Items.ToList();
                    yield break;
                }

                bool foundStart = false;
                bool foundEnd = false;
                foreach (var start in startList)
                {
                    foundStart = true;
                    foundEnd = false;
                    foreach (var end in endList)
                    {
                        if (start == end)
                            continue;

                        foundEnd = true;

                        int[] result = new int[g1.Items.Count];


                        result[0] = start;
                        result[endIndex] = end;
                        var diff = g1.Items.Except(new[] { start, end });
                        var currentIndex = 1;
                        foreach (var diffItem in diff)
                        {
                            result[currentIndex] = diffItem;
                            currentIndex++;
                        }

                        yield return result.ToList();
                    }

                    if (!foundEnd)
                    {
                        yield return start.Yield().Union(g1.Items.Except(new[] { start })).ToList();
                    }
                }

                if (!foundStart)
                {
                    foreach (var end in endList)
                    {
                        foundEnd = true;
                        yield return (g1.Items.Except(new[] { end })).Union(end.Yield()).ToList();
                    }
                }

                if (!foundStart && !foundEnd)
                {
                    yield return g1.Items.ToList();
                }
            }
        }
    }


    public class Group
    {
        public HashSet<int> Items = new HashSet<int>();

        public Group(IEnumerable<int> items)
        {
            foreach (var item in items)
                this.Items.Add(item);
        }

    }
}

